package org.lookahead.parent.common.utils;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Date;

/**
 * @Description AES 256 加解密
 * @Author liucheng
 * @Date 2022/2/22 13:41
 * @Version 1.0
 */
public class Aes256Util {


    /***默认向量常量**/
    public static final String IV = "lookahead2122132";
    /***加密方式AES**/
    public static final String AES = "AES";
    /***加密模式**/
    public static final String EN_MODEL = "AES/CBC/PKCS7Padding"; // "算法/加密/填充"
    /***字节数组读取缓冲大小 加密**/
    public static final Integer EN_BUFFER = 65535;
    /***字节数组读取缓冲大小 解密**/
    public static final Integer DE_BUFFER = 65536;

    private final static Logger logger = LoggerFactory.getLogger(Aes256Util.class);

    /*
      使用PKCS7Padding填充必须添加一个支持PKCS7Padding的Provider
      类加载的时候就判断是否已经有支持256位的Provider,如果没有则添加进去
     */
    static {
        if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
            Security.addProvider(new BouncyCastleProvider());
        }
    }

    /**
     * @param pkey       密匙
     * @param sourceFile 需加密文件路径地址
     * @param destFile   加密后输出到目标文件地址
     * @param flag       加解密方式： true加密   false解密
     */
    public static void AES(String pkey, String sourceFile, String destFile, boolean flag) {

        // 开始时间
//		Date startDate = DateUtil.now();

        InputStream in = null;
        OutputStream out = null;

        try {
            File df = new File(destFile);
            if (!df.getParentFile().exists()) {
                boolean mkdirs = df.getParentFile().mkdirs();
            }
            // 源文件流
            in = Files.newInputStream(Paths.get(sourceFile));
            // 输出目标文件流
            out = Files.newOutputStream(Paths.get(destFile));
            // 加密65535 解密65536字节数设置缓冲区 - 64k
            byte[] bytes = new byte[flag ? EN_BUFFER : DE_BUFFER];
            int len = -1;
            // 密钥为AES256
            SecretKeySpec skey = new SecretKeySpec(pkey.getBytes(), AES);
            // "算法/加密/填充"
            Cipher cipher = Cipher.getInstance(EN_MODEL);
            // CBC模式设置向量
            IvParameterSpec iv = new IvParameterSpec(IV.getBytes());
            // 设置加解密参数：flag ture为加密ENCRYPT_MODE  false解密DECRYPT_MODE
            int enDecr = flag ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE;
            //初始化加密器
            cipher.init(enDecr, skey, iv);
            if (flag) {
                // 加密
                if (in.available() >= Integer.MAX_VALUE) {
                    throw new Exception("文件太大！");
                }
                int size = Func.toInt(in.available());
                byte[] b = new byte[4];
                b[0] = (byte) ((size >> 24) & 0xFF);
                b[1] = (byte) ((size >> 16) & 0xFF);
                b[2] = (byte) ((size >> 8) & 0xFF);
                b[3] = (byte) (size & 0xFF);
                out.write(b);
            } else {
                // 解密
                byte[] enByte = new byte[4];
                int read = in.read(enByte);
            }
            while ((len = in.read(bytes)) != -1) {
                out.write(cipher.update(bytes, 0, len));
                out.write(cipher.doFinal());
            }
        } catch (Exception e) {
            logger.info("aesEncrypt() method error:", e);
        } finally {
            try {
                if (null != out) {
                    out.flush();
                }
            } catch (IOException ignored) {
            }
            try {
                if (in != null) {
                    in.close();
                }
            } catch (IOException ignored) {
            }
            try {
                if (null != out) {
                    out.close();
                }
            } catch (IOException ignored) {
            }
        }
//		System.out.println((flag ? "加" : "解") + ("密时间：" + DateUtil.between(startDate, DateUtil.now())));
    }

    /**
     * 获得密钥
     *
     * @param secretKey
     * @return
     * @throws Exception
     */
    public static SecretKey generateKey(String secretKey) throws Exception {
        //防止linux下 随机生成key
        Provider p = Security.getProvider("SUN");
        SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG", p);
        secureRandom.setSeed(secretKey.getBytes());
        KeyGenerator kg = KeyGenerator.getInstance("AES");
        kg.init(secureRandom);
        // 生成密钥
        return kg.generateKey();
    }

    /**
     * <p>
     * 文件加密
     * </p>
     *
     * @param key
     * @param sourceFilePath
     * @param destFilePath
     * @throws Exception
     */
    public static void encryptFile(String key, String sourceFilePath, String destFilePath) throws Exception {
        File sourceFile = new File(sourceFilePath);
        File destFile = new File(destFilePath);
        if (sourceFile.exists() && sourceFile.isFile()) {
            if (!destFile.getParentFile().exists()) {
                destFile.getParentFile().mkdirs();
            }
            destFile.createNewFile();
            InputStream in = new FileInputStream(sourceFile);
            OutputStream out = new FileOutputStream(destFile);
            SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(), "AES");
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
            IvParameterSpec iv = new IvParameterSpec(IV.getBytes());
            cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, iv);//初始化加密器

            CipherInputStream cin = new CipherInputStream(in, cipher);
            byte[] cache = new byte[65536];
            int nRead = 0;
            while ((nRead = cin.read(cache)) != -1) {
                out.write(cache, 0, nRead);
                out.flush();
            }
            out.close();
            cin.close();
            in.close();
        }
    }


    /**
     * <p>
     * 文件解密
     * </p>
     *
     * @param key
     * @param sourceFilePath
     * @param destFilePath
     * @throws Exception
     */
    public static void decryptFile(String key, String sourceFilePath, String destFilePath) throws Exception {
        File sourceFile = new File(sourceFilePath);
        File destFile = new File(destFilePath);
        if (sourceFile.exists() && sourceFile.isFile()) {
            if (!destFile.getParentFile().exists()) {
                destFile.getParentFile().mkdirs();
            }
            destFile.createNewFile();
            FileInputStream in = new FileInputStream(sourceFile);
            FileOutputStream out = new FileOutputStream(destFile);
            SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(), "AES");
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
            IvParameterSpec iv = new IvParameterSpec(IV.getBytes());
            cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, iv);
            CipherOutputStream cout = new CipherOutputStream(out, cipher);
            byte[] cache = new byte[65536];
            int nRead = 0;
            while ((nRead = in.read(cache)) != -1) {
                cout.write(cache, 0, nRead);
                cout.flush();
            }
            cout.close();
            out.close();
            in.close();
        }
    }

    public static String getRandom(int length) {
        char[] arr = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k',
                'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
        String result = String.valueOf(arr[(int) Math.floor(Math.random() * 36)]);
        for (int i = 1; i < length; i++) {
            result += arr[(int) Math.floor(Math.random() * 36)];
        }
        return result;
    }


    public static void main(String[] args) throws Exception {
//		System.out.println(getRandom(16));
//		System.out.println(getRandom(32));

        Date date = DateUtil.now();
        String aaa = AesUtil.decrypt("RLdLpJzbGxd3mmoSMKuekEYA1tDIBvnfAintCoO1W8Fsf6kRpa1uWn0zx3zzC2ew", Func.md5_16bit(Func.toStr(1536327503235334145L)));
        String cKey = aaa;

//		String path = "C:\\Users\\liuc\\Desktop\\1536327503235334145";		//要遍历的路径
//		File file = new File(path);		//获取其file对象
//		func(cKey, file, path.length(), "C:\\Users\\liuc\\Desktop\\1536327503235334145.de");

        System.out.println("一共加解密时间：" + DateUtil.between(date, DateUtil.now()));

        AES(cKey, "d:\\1111.txt", "d:\\1111.en.txt", true);

//		encryptFile(cKey, "C:\\Users\\15650\\Desktop\\竹里馆1.mp4", "C:\\Users\\15650\\Desktop\\EncryptedZlg.mp4");

//		decryptFile(cKey, "C:\\Users\\15650\\Desktop\\1.txt.en", "C:\\Users\\15650\\Desktop\\Decrypt11.txt");

    }

    public static void EnAes(String cKey, String enPath, String destPath) {
        try {
            Date date = DateUtil.now();
            File file = new File(enPath);        //获取其file对象
            func(cKey, file, enPath.length(), destPath);
            System.out.println("一共加密时间：" + DateUtil.between(date, DateUtil.now()));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static void func(String cKey, File file, Integer length, String destPath) throws IOException {
        File[] fs = file.listFiles();
        for (File f : fs) {
            if (f.isDirectory()) {    //若是目录，则递归打印该目录下的文件
                func(cKey, f, length, destPath);
            }
            if (f.isFile()) {        //若是文件，直接打印
                String destPath2 = destPath + StringUtil.sub(f.getPath(), length, f.getPath().length());
                System.out.println("cKey:" + cKey);
                System.out.println("f.getPath():" + f.getPath());
                System.out.println("destPath2:" + destPath2);
                AES(cKey, f.getPath(), destPath2, true);
            }
        }
    }

}
